package com.twj.tools.poi;

import com.twj.tools.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.model.PicturesTable;
import org.apache.poi.hwpf.usermodel.Picture;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigInteger;
import java.net.URL;
import java.util.List;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author 作者: tianwenju
 * @version 1.0
 * @project 金税三期工程核心征管及应用总集成项目
 * @package com.css.dsj.xmgl.tbrygl.modules.xmryfp.service.impl
 * @file WordUtil.java 创建时间:2025年03月24日 16:48
 * @title 标题（要求能简洁地表达出类的功能和职责）
 * @description 描述（简要描述类的职责、实现方式、使用注意事项等）
 * @copyright Copyright (c) 2020 中国软件与技术服务股份有限公司
 * @company 中国软件与技术服务股份有限公司
 * @module 模块: word生成工具类  主要用于简历生成 模板生成
 * @reviewer 审核人:
 * @history 修订历史（历次修订内容、修订人、修订时间等）
 */
@Slf4j
public class WordUtil {
    /**
     * 图片显示的最大宽度
     */
    private static final int PIC_MAX_WIDTH = 400;

    /**
     * 定义支持的图片格式
     */
    private static final Set<String> SUPPORTED_FORMATS = Arrays.stream(new String[]{"jpg", "jpeg", "png", "gif"}).map(String::toLowerCase).collect(Collectors.toSet());

    /**
     * @param tableMap     替换参数集
     * @param templatePath 简历模板文件
     * @param outputPath   导出路径
     * @description 简历文件导出
     * @time 创建时间：2025/4/15 17:12
     * @author 作者：tianwenju
     * @history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public static void exportWord(Map<String, Object> tableMap, String templatePath, String outputPath) throws IOException {

        // 参数校验
        if (templatePath == null || templatePath.isEmpty()) {
            throw new BusinessException("简历导出模板路径不能为空!!!");
        }
        log.info("templatePath => {}", templatePath);
        log.info("outputPath => {}", outputPath);

        // 使用 try-with-resources 确保资源关闭
        try (FileInputStream fis = new FileInputStream(templatePath); XWPFDocument document = new XWPFDocument(fis); ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); FileOutputStream fos = new FileOutputStream(outputPath)) {
            // 修改表格内容
            changeTableText(document, tableMap);

            // 将文档写入字节流
            document.write(byteOut);

            // 将字节流内容写入输出文件
            byteOut.writeTo(fos);
        } catch (IOException e) {
            // 记录异常信息
            log.error("简历导出失败！！！");
            e.printStackTrace();
            throw new BusinessException("简历导出失败!!!" + e.getMessage() + outputPath);
        }
    }

    /**
     * 替换文档中段落文本
     *
     * @param document docx解析对象
     * @param textMap  需要替换的信息集合
     */
    public static void changeParagraphText(XWPFDocument document, Map<String, Object> textMap) {
        //获取段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            //判断此段落时候需要进行替换
            String text = paragraph.getText();
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    run.setText(changeValue(run.toString(), textMap), 0);
                }
            }
        }
    }


    /**
     * 替换表格对象方法
     *
     * @param document docx解析对象
     * @param textMap  需要替换的信息集合
     */
    public static void changeTableText(XWPFDocument document, Map<String, Object> textMap) {
        //获取表格对象集合
        List<XWPFTable> tables = document.getTables();
        for (int i = 0; i < tables.size(); i++) {
            XWPFTable table = tables.get(i);
            //判断表格是需要替换还是需要插入，判断逻辑有$为替换，表格无$为插入
            if (checkText(table.getText())) {
                List<XWPFTableRow> rows = table.getRows();
                //遍历表格,并替换模板
                eachTable(rows, textMap);
            }
        }
    }

    /**
     * 遍历表格,并替换模板
     *
     * @param rows    表格行对象
     * @param textMap 需要替换的信息集合
     */
    public static void eachTable(List<XWPFTableRow> rows, Map<String, Object> textMap) {
        for (XWPFTableRow row : rows) {
            List<XWPFTableCell> cells = row.getTableCells();
            for (XWPFTableCell cell : cells) {
                // 判断单元格是否需要替换
                if (checkText(cell.getText())) {
                    List<XWPFParagraph> paragraphs = cell.getParagraphs();
                    for (XWPFParagraph paragraph : paragraphs) {
                        List<XWPFRun> runs = paragraph.getRuns();
                        // 提取该段落的所有文本
                        StringBuilder fullText = new StringBuilder();
                        for (XWPFRun run : runs) {
                            fullText.append(run.getText(0));
                        }
                        String text = fullText.toString();
                        String replacedText = changeValue(text, textMap);
                        if (isUrlAddress(replacedText)) {
                            // 清除现有runs
                            for (int i = runs.size() - 1; i >= 0; i--) {
                                paragraph.removeRun(i);
                            }
                            // 插入图片
                            insertImage(paragraph, replacedText);
                        } else if (!replacedText.equals(text)) {

                            // 清除现有runs
                            for (int i = runs.size() - 1; i >= 0; i--) {
                                paragraph.removeRun(i);
                            }
                            // 添加新的run带有替换后的文本
                            XWPFRun newRun = paragraph.createRun();
                            newRun.setText(replacedText, 0);
                        }
                    }
                }
            }
        }
    }

    /**
     * 匹配传入信息集合与模板
     *
     * @param text    模板需要替换的区域
     * @param textMap 传入信息集合
     * @return 模板需要替换区域信息集合对应值
     */
    public static String changeValue(String text, Map<String, Object> textMap) {
        Set<Map.Entry<String, Object>> textSets = textMap.entrySet();
        System.out.println("value 替换集 -->:" + textSets);
        System.out.println("替换前的 value -->:" + text);
        if (text.contains("[") && text.contains("]")) {
            text = dealOptionalParam(text, textMap);
        }
        for (Map.Entry<String, Object> entry : textSets) {
            String placeHolder = entry.getKey();
            if (text.contains(placeHolder)) {
                Object replaceVal = entry.getValue();
                text = text.replace(placeHolder, ObjectUtils.isEmpty(replaceVal) ? "无" : replaceVal.toString());
                System.out.println("替换后的 ->" + text);
            }
        }
        return text;
    }

    /**
     * 处理可选参数[]
     *
     * @param text    文本
     * @param textMap 文本替换集合
     */
    public static String dealOptionalParam(String text, Map<String, Object> textMap) {
        Pattern pattern = Pattern.compile("\\[(.*?)\\]");
        Matcher matcher = pattern.matcher(text);
        // 可能包含多个[]
        while (matcher.find()) {
            String tag = matcher.group(0);// 匹配到的完整标签
            String name = matcher.group(1);// 提取的标签名
            System.out.println(" 解析可选项[]-->" + String.format("tag: %s  name: %s", tag, name));
            // tag: [获得<认证证书>认证。]   name: 获得<认证证书>认证。
            //  tag:<认证证书>  name:认证证书
            Pattern p = Pattern.compile("<([^>]+)>");
            Matcher m = p.matcher(name);
            while (m.find()) {
                String placeHolder = m.group(0);
                Object replaceVal = textMap.get(placeHolder);
                if (!StringUtils.isEmpty(replaceVal)) {
                    text = text.replace(tag, name).replace(placeHolder, replaceVal.toString());
                    break;
                } else {
                    text = text.replace(tag, "");
                }
            }
        }
        return text;
    }

    /**
     * 判断文本中时候包含$ <
     *
     * @param text 文本
     * @return 包含返回true, 不包含返回false
     */
    public static boolean checkText(String text) {

        return text.indexOf("<") != -1;
    }

    /**
     * @param templateFile 模板
     * @return 占位符集合
     * @description 收集模板中的占位符信息
     * @time 创建时间：2025/3/31 11:34
     * @author 作者：tianwenju
     * @history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public static Set<String> collectTemplatePlaceholders(File templateFile) throws IOException {

        Set<String> placeholders = new HashSet<>();
        Pattern pattern = Pattern.compile("<([^>]+)>");
        FileInputStream fileInputStream = new FileInputStream(templateFile);
        XWPFDocument document = new XWPFDocument(fileInputStream);
        List<XWPFTable> tables = document.getTables();
        for (XWPFTable table : tables) {
            List<XWPFTableRow> rows = table.getRows();
            for (XWPFTableRow row : rows) {
                for (XWPFTableCell cell : row.getTableCells()) {
                    String cellText = cell.getText();
                    Matcher matcher = pattern.matcher(cellText);
                    while (matcher.find()) {
                        placeholders.add(matcher.group(1));
                    }
                }
            }
        }
        fileInputStream.close();

        log.info("placeholders = " + placeholders);
        log.info("placeholders size= " + placeholders.size());
        return placeholders;
    }


    /**
     * @param paragraph 段落
     * @param address   地址  http://....
     * @description 段落中插入图片
     * @time 创建时间：2025/4/15 14:45
     * @author 作者：tianwenju
     * @history 修订历史（历次修订内容、修订人、修订时间等）
     */
    private static void insertImage(XWPFParagraph paragraph, String address) {

        try {
            // 根据地址获取图片路径
            List<String> imgPathList = getImagePathByAddress(address);
            for (String imagePath : imgPathList) {
                //文件格式：jpg png gif jpeg
                String suffix = imagePath.substring(imagePath.lastIndexOf(".") + 1).toLowerCase();
                // 参数校验
                if (suffix == null || suffix.isEmpty() || !SUPPORTED_FORMATS.contains(suffix.toLowerCase())) {
                    throw new BusinessException("不支持该图片格式: " + suffix);
                }
                BufferedImage originalImage;
                if (!isUrlAddress(imagePath)) {
                    System.out.println("-----附件中抽取到本地图片-----imagePath ----------> " + imagePath);
                    //本地图片地址
                    //简化资源管理：使用 try-with-resources 确保所有流在使用完毕后自动关闭，避免手动关闭流带来的潜在问题
                    try (InputStream imageStream = new FileInputStream(imagePath)) {
                        originalImage = ImageIO.read(imageStream);
                    }
                } else {
                    originalImage = ImageIO.read(new URL(imagePath));
                }


                if (originalImage == null) {
                    throw new BusinessException("图片读取失败！！" + imagePath);
                }

                int width = originalImage.getWidth();
                int height = originalImage.getHeight();
                //设置压缩后的宽度和高度
                if (width > PIC_MAX_WIDTH) {
                    int targetWidth = getTableMaxWidth(paragraph.getDocument());//压缩宽度
                    int targetHeight = (int) (height * ((double) targetWidth / width));
                    height = (int) (height * ((double) PIC_MAX_WIDTH / width));
                    width = PIC_MAX_WIDTH; // 更新展示宽度
                    originalImage = compressPicture(originalImage, targetWidth, targetHeight);
                }

                try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                    ImageIO.write(originalImage, suffix, baos);
                    baos.flush();
                    try (InputStream stream = new ByteArrayInputStream(baos.toByteArray())) {
                        paragraph.createRun().addPicture(stream, getPictureType(suffix), imagePath, Units.toEMU(width), Units.toEMU(height));
                    }
                }
            }
        } catch (Exception e) {

            log.error("图片插入失败：" + e.getMessage());
            e.printStackTrace();
            //throw new BusinessException("图片插入失败：" + e.getMessage());
        }
    }

    /**
     * @param originalImage 原始图片
     * @param targetWidth   目标宽度
     * @param targetHeight  目标高度
     * @return 压缩图片流
     * @description 图片压缩
     * @time 创建时间：2025/4/15 16:45
     * @author 作者：tianwenju
     * @history 修订历史（历次修订内容、修订人、修订时间等）
     */
    private static BufferedImage compressPicture(BufferedImage originalImage, int targetWidth, int targetHeight) throws IOException {

        // 创建压缩后的图片
        BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);

        Graphics2D g = resizedImage.createGraphics();
        try {
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.setBackground(Color.WHITE); // 设置背景色为白色
            g.clearRect(0, 0, targetWidth, targetHeight); // 清除黑边
            g.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
            return resizedImage;
        } finally {
            g.dispose(); // 确保释放 Graphics2D 资源
        }
    }

    /**
     * @param document 文档
     * @return int  获取表格宽度 像素
     * @description 相关说明
     * @time 创建时间：2025/4/14 17:20
     * @author 作者：tianwenju
     * @history 修订历史（历次修订内容、修订人、修订时间等）
     */
    private static int getTableMaxWidth(XWPFDocument document) {

        CTSectPr sectPr = document.getDocument().getBody().getSectPr();
        CTPageMar pageMar = sectPr.getPgMar();
        CTPageSz pageSz = sectPr.getPgSz();

        // 获取左右边距和页面宽度  返回的是以 twips 为单位的值，而非像素
        // twips（Twentieth of an Inch Point，即 1/20 磅）
        //Twips 是 Word 文档中常用的测量单位，1 磅 = 1/72 英寸，因此 1 twip = 1/1440 英寸
        BigInteger leftMarginTwips = (BigInteger) pageMar.getLeft(); // 左边距
        BigInteger rightMarginTwips = (BigInteger) pageMar.getRight(); // 右边距
        BigInteger pageWidthTwips = (BigInteger) pageSz.getW(); // 页面宽度

        // 使用 intValueExact() 方法将 BigInteger 转换为 int，避免数据丢失
        //在处理文档布局时，建议统一使用 EMU 单位，因为它是 Apache POI 和 Open XML 标准中推荐的单位
        int leftMargin = leftMarginTwips.intValueExact();
        int rightMargin = rightMarginTwips.intValueExact();
        int pageWidth = pageWidthTwips.intValueExact();
        // 计算表格最大宽度  转换为像素: twips * 96 / 1440    DPI（每英寸点数，默认通常为 96 DPI）
        // 表格宽度 大概553px
        return (pageWidth - leftMargin - rightMargin) * 96 / 1440;
    }

    private static int getPictureType(String picType) {
        int res = XWPFDocument.PICTURE_TYPE_PICT;
        if (picType != null) {
            if (picType.equalsIgnoreCase("png")) {
                res = XWPFDocument.PICTURE_TYPE_PNG;
            } else if (picType.equalsIgnoreCase("gif")) {
                res = XWPFDocument.PICTURE_TYPE_GIF;
            } else if (picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")) {
                res = XWPFDocument.PICTURE_TYPE_JPEG;
            }
        }
        return res;
    }

    /**
     * @return 判断是否是地址
     */
    private static boolean isUrlAddress(String text) {
        // 这里可以添加更复杂的地址检测逻辑
        return text.startsWith("http://") || text.startsWith("https://");
    }

    /**
     * @param address 附件地址
     * @return 地址集合
     * @description 地址转换
     * 1.可能存在多个图片地址用;连接，需要转成列表
     * 2.在Word或者PDF中，需要将图片提取出来暂存本地，并返回一个列表
     * @time 创建时间：2025/4/17 10:40
     * @author 作者：tianwenju
     * @history 修订历史（历次修订内容、修订人、修订时间等）
     */
    private static List<String> getImagePathByAddress(String address) throws IOException {

        List<String> imagePaths = new ArrayList<>();


        // 这里可以添加根据地址获取图片路径的逻辑
        //TODO: 测试用  在前端 public文件夹添加测试文件
        if (address.contains("https")) {
//            return "D:\\test\\pic\\系统架构师.png";
//            return "http://127.0.0.1:8080/XTJGS.png";
            imagePaths.add("http://127.0.0.1:8080/HJ.gif");
        } else if (address.endsWith(".docx")) {
            // 提取docx中的图片
            return extractImageFromDocx("http://127.0.0.1:8080/FJ.docx");
        } else if (address.endsWith(".pdf")) {
            // 提取pdf中的图片
            return extractImageFromPDF("http://127.0.0.1:8080/FJ.docx");
        } else {
//            return "D:\\test\\pic\\PMP.jpeg";
//            return "http://127.0.0.1:8080/PMP.jpeg";
//            return "http://127.0.0.1:8080/b.jpg";
            return extractImageFromDocx("http://127.0.0.1:8080/FJ.docx");
//           return Arrays.stream(address.split(";")).collect(Collectors.toList());
        }
// TODO: 2025/4/16 没有特殊处理直接返回
        return imagePaths;
    }

    /**
     * @param docxPath docx文件URL路径
     * @return 提取的图片路径列表 暂存本地
     * @description 网络获取docx附件 使用Apache POI读取并提取图片 暂存本地
     * @time 创建时间：2025/4/17 9:55
     * @author 作者：tianwenju
     * @history 修订历史（历次修订内容、修订人、修订时间等）
     */
    private static List<String> extractImageFromDocx(String docxPath) throws IOException {
        List<String> imagePaths = new ArrayList<>();
        try (InputStream is = new URL(docxPath).openStream(); XWPFDocument document = new XWPFDocument(is)) {
            List<XWPFPictureData> pictures = document.getAllPictures();
            if (!pictures.isEmpty()) {
                for (XWPFPictureData picture : pictures) {
                    String[] names = picture.getFileName().split("\\.");// 临时保存图片
                    File imgFile = File.createTempFile(names[0], "." + names[1]);
                    try (FileOutputStream fos = new FileOutputStream(imgFile)) {
                        fos.write(picture.getData());
                    }
                    imagePaths.add(imgFile.getAbsolutePath());
                }

            }

            return imagePaths;
        }
    }
    /**
     * @param pdfPath pdf文件URL路径
     * @return 提取的图片路径列表 暂存本地
     * @description 网络获取pdf附件 使用Apache PDFBox读取并提取图片 暂存本地
     * @time 创建时间：2025/4/17 9:55
     * @author 作者：tianwenju
     * @history 修订历史（历次修订内容、修订人、修订时间等）
     */
    private static List<String> extractImageFromPDF(String pdfPath) throws IOException {

        //直接将PDF转图片 非常耗时
        /*List<String> imagePaths = new ArrayList<>();
        try (InputStream is = new URL(pdfPath).openStream(); PDDocument document = PDDocument.load(is)) {
            PDFRenderer pdfRenderer = new PDFRenderer(document);
            for (int page = 0; page < document.getNumberOfPages(); ++page) {
                BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300); // 300 DPI
                File imgFile = File.createTempFile("pdf-image-" + page, ".png");
                try (FileOutputStream fos = new FileOutputStream(imgFile)) {
                    ImageIO.write(bim, "png", fos);
                }
                imagePaths.add(imgFile.getAbsolutePath());
            }
        }
        return imagePaths;*/

        List<String> imagePaths = new ArrayList<>();
        try (InputStream is = new URL(pdfPath).openStream(); PDDocument document = PDDocument.load(is)) {
            PDFImageExtractor imageExtractor = new PDFImageExtractor();
            imagePaths = imageExtractor.extractImages(document);
        }
        return imagePaths;
    }
    private static List<String> extractImageFromDoc(String docPath) throws IOException {
        List<String> imagePaths = new ArrayList<>();
        try (InputStream is = new URL(docPath).openStream(); HWPFDocument document = new HWPFDocument(is)) {
            PicturesTable picturesTable = document.getPicturesTable();
            if (picturesTable != null) {
                List<Picture> allPictures = picturesTable.getAllPictures();
                int c = 0;
                for (Picture picture : allPictures) {
                    String fileName = "doc-img-" + (c++) + "." + picture.suggestFileExtension();
                    File imgFile = File.createTempFile(fileName, "." + picture.suggestFileExtension());
                    try (FileOutputStream fos = new FileOutputStream(imgFile)) {
                        fos.write(picture.getContent());
                    }
                    imagePaths.add(imgFile.getAbsolutePath());
                }
            }
        } catch (Exception e) {
            log.info("简历附件-Word图片获取失败:" + e.getMessage() + "，请检查附件是否为doc格式 ->" + docPath);
            e.printStackTrace();
        }
        return imagePaths;
    }

    private static void export(File file) {
        String fileName = file.getName().substring(0, file.getName().indexOf('.'));

        PDDocument document = null;

        try {
            //解析PDF并加载
            document =  PDDocument.load(file);
            //获取索引为0的页面（也就是获取第一页）
            PDPage page = document.getPage(0); // 可以通过获取页数进行循环每一页
            //获取页面所需的任何资源的字典
            PDResources resources = page.getResources();
            //返回XObject资源的名称
            Iterable<COSName> cosNames = resources.getXObjectNames();
            if(cosNames != null) {
                Iterator<COSName> cosNameIterator = cosNames.iterator();
                while (cosNameIterator.hasNext()) {
                    COSName cosName = cosNameIterator.next();
                    //判断给定名称的XObject资源是否为图像
                    if(resources.isImageXObject(cosName)) {
                        PDImageXObject pdImage = (PDImageXObject) resources.getXObject(cosName);
                        BufferedImage image = pdImage.getImage();
                        try(FileOutputStream fileOutputStream = new FileOutputStream(file.getParentFile().getAbsolutePath()+ File.separator + fileName + ".jpg")) {
                            //输出图片到指定位置
                            ImageIO.write(image, "jpg", fileOutputStream);
                            fileOutputStream.flush();
                        } catch (IOException e) {
                            System.out.println("输出文件失败：" + fileName + e.getMessage());
                        }
                    }
                }
            }
        } catch (IOException e) {
            System.out.println("提取失败：" + fileName);
        } finally {
            try {
                document.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


    public static void main(String[] args) throws Exception {

//        testGenerateDoc();
        File templateFile = new File("d:\\test\\template-ryll-1.docx");
        Set<String> list = collectTemplatePlaceholders(templateFile);
//        获取<>中的值
    }

    /**
     * @throws IOException 测试简历替换生成
     */
    private static void testGenerateDoc() throws IOException {
        Map<String, Object> tableMap = new HashMap<>();
        tableMap.put("xm", "赵云");
        tableMap.put("xb", "男");
        tableMap.put("csrq", "2020-01-01");
        tableMap.put("xl", "博士");
        tableMap.put("byxx", "东大");
        tableMap.put("jszc", "高级工程师");
        tableMap.put("gszw", "经理");
        tableMap.put("rzsj", "9年");
        tableMap.put("bxmrz", "总监");
        tableMap.put("ryjb", "高级");
        tableMap.put("rzzs", "高级架构师");

        tableMap.put("binf", "2016");//毕业年份
        tableMap.put("byxx", "东方大学");//毕业学校
        tableMap.put("zy", "计算机科学与技术");//专业
        tableMap.put("rznf", "2016");//入职年份

        tableMap.put("gl", "9");//工龄
        tableMap.put("byzsfj", "9");//工龄


        String templatePath = "d:\\test\\template-ryjl-1.docx";
        String outputPath = "d:/test/template-ryjl-1-generate.docx";
        exportWord(tableMap, templatePath, outputPath);
    }

}